#include "httpresponse.h"

const int DEFAULT_CODE = -1;

// 文件后缀名与对应的 MIME 类型之间的映射关系
const std::unordered_map<std::string, std::string> HttpResponse::suffixType_
{   
    { ".html",  "text/html" },
    { ".xml",   "text/xml" },
    { ".xhtml", "application/xhtml+xml" },
    { ".txt",   "text/plain" },
    { ".rtf",   "application/rtf" },
    { ".pdf",   "application/pdf" },
    { ".word",  "application/nsword" },
    { ".png",   "image/png" },
    { ".gif",   "image/gif" },
    { ".jpg",   "image/jpeg" },
    { ".jpeg",  "image/jpeg" },
    { ".au",    "audio/basic" },
    { ".mpeg",  "video/mpeg" },
    { ".mpg",   "video/mpeg" },
    { ".avi",   "video/x-msvideo" },
    { ".gz",    "application/x-gzip" },
    { ".tar",   "application/x-tar" },
    { ".css",   "text/css"},
    { ".js",    "text/javascript"},
};

// 状态码与响应状态的映射关系
const std::unordered_map<int, std::string> HttpResponse::codeStatus_
{
    { 200, "OK" },
    { 400, "Bad Request" },
    { 403, "Forbidden" },
    { 404, "Not Found" },
};

// HTTP 状态码与对应的 HTML 页面路径
const std::unordered_map<int, std::string> HttpResponse::codePath_
{
    { 400, "/400.html" },
    { 403, "/403.html" },
    { 404, "/404.html" },
};

HttpResponse::HttpResponse()
{
    code_ = DEFAULT_CODE;
    path_ = "";
    srcDir_ = "";

    isKeepAlive_ = false;
    mmFile_ = nullptr;
    mmFileStat_ = {0};
}

HttpResponse::~HttpResponse()
{
    unmapFile();
}

// 初始化
void HttpResponse::init(const std::string& srcDir, std::string& path, 
                        bool isKeepAlive = false, int code = DEFAULT_CODE)
{
    assert(srcDir != "");
    if(mmFile_) 
        unmapFile();
    code_ = code;
    isKeepAlive_ = isKeepAlive;
    path_ = path;
    srcDir_ = srcDir;
    mmFile_ = nullptr;
    mmFileStat_ = {0};
}

// 根据请求的资源文件情况生成完整的 HTTP 响应
void HttpResponse::makeResponse(Buffer& buff)
{
    // 获取文件状态并且判断是不是目录
    if(stat((srcDir_ + path_).data(), &mmFileStat_) < 0 || S_ISDIR(mmFileStat_.st_mode)) {
        code_ = static_cast<int>(HttpStatusCode::NOT_FOUND);
    // 检查文件的权限
    } else if(!(mmFileStat_.st_mode & S_IROTH)) {
        code_ = static_cast<int>(HttpStatusCode::FORBIDDEN);
    } else if(code_ == DEFAULT_CODE) {
        code_ = static_cast<int>(HttpStatusCode::OK);
    }

    // 生成对应的错误页面
    errorHtml();
    //  添加状态行
    addResponeStateLine(buff);
    // 添加响应头部
    addResponeHeader(buff);
    // 添加响应内容
    addResponeContent(buff);
}

// 获取状态码
int HttpResponse::getCode() const
{
    return code_;
}

// 获取映射到内存中的文件内容的起始地址
char* HttpResponse::file()
{
    return mmFile_;
}

// 获取文件的大小
size_t HttpResponse::fileLen() const
{
    return mmFileStat_.st_size;
}

// 生成对应的错误页面
void HttpResponse::errorHtml()
{
    if(codePath_.count(code_) == 1) {
        // 更新请求路径并获取文件状态
        path_ = codePath_.find(code_)->second;
        stat((srcDir_ + path_).data(), &mmFileStat_);       
    }
}

// 生成 HTTP 响应的状态行
void HttpResponse::addResponeStateLine(Buffer& buff)
{
    std::string status;
    if(codeStatus_.count(code_) == 1) {
        status = codeStatus_.find(code_)->second;
    } else {
        code_ = static_cast<int>(HttpStatusCode::BAD_REQUEST);
        status = codeStatus_.find(static_cast<int>(HttpStatusCode::BAD_REQUEST))->second;
    }
    buff.append("HTTP/1.1 " + std::to_string(code_) + " " + status + "\r\n");
}

// 构建了 HTTP 响应的部分头部信息
void HttpResponse::addResponeHeader(Buffer& buff)
{
    buff.append("Connection: ");
    // 长连接, 在这个长连接上最多可以进行 6 次请求和响应, 超时时间为120s
    if(isKeepAlive_) {
        buff.append("keep-alive\r\n");
        buff.append("keep-alive: max=6, timeout=120\r\n");
    } else {
        buff.append("close\r\n");
    }
    // 响应内容的类型
    buff.append("Content-type: " + getFileType() + "\r\n");
}

// 将请求的文件内容映射到内存中，并将文件的长度信息添加到 HTTP 响应头中
void HttpResponse::addResponeContent(Buffer& buff)
{
    int srcFd = open((srcDir_ + path_).data(), 0, O_RDONLY);
    if(srcFd < 0) {
        errorContent(buff, "File NotFound!");
        return;
    }

    // 将文件内容映射到内存中，提高访问效率
    LOG_DEBUG("file path : %s", (srcDir_ + path_).data());
    void* mmRet = mmap(0, mmFileStat_.st_size, PROT_READ, MAP_PRIVATE, srcFd, 0);
    if(mmRet == MAP_FAILED) {
        close(srcFd);
        errorContent(buff, "file mmap error!");
        return;
    }

    mmFile_ = static_cast<char*>(mmRet);
    close(srcFd);
    buff.append("Content-length: " + std::to_string(mmFileStat_.st_size) + "\r\n\r\n");
}

// 解除文件映射
void HttpResponse::unmapFile()
{
    if(mmFile_) {
        munmap(mmFile_, mmFileStat_.st_size);
        mmFile_ = nullptr;
    }
}

// 根据文件路径的后缀确定文件的类型
std::string HttpResponse::getFileType()
{
    // 查找最后一个.的位置，用来分割后缀
    std::string::size_type index = path_.find_last_of('.');
    if(index == std::string::npos) {
        // 没有找到，返回纯文本格式，即不包含任何的格式信息
        return "text/plain";
    }

    // 获取文件后缀名
    std::string suffix = path_.substr(index);
    if(suffixType_.count(suffix) == 1) {
        return suffixType_.find(suffix)->second;
    }
    return "text/plain";
}

// 生成一个包含错误信息的 HTML 页面
void HttpResponse::errorContent(Buffer& buff, std::string message)
{
    std::string body;
    std::string status;
    // 构建 HTML 页面头部
    body += "<html><title>Error</title>";
    body += "<body bgcolor=\"ffffff\">";
    if(codeStatus_.count(code_) == 1) {
        status = codeStatus_.find(code_)->second;
    } else {
        status = "Bad Request";
    }

    // 构建 HTML 页面中的错误信息部分
    body += std::to_string(code_) + " : " + status  + "\n";
    body += "<p>" + message + "</p>";
    body += "<hr><em>TinyWebServer</em></body></html>";

    buff.append("Content-length: " + std::to_string(body.size()) + "\r\n\r\n");
    buff.append(body);
}